//-------------------------------------------------------------------
//	Copyright (c) 2013-2014  Zhirnov Andrey
//	This file is part of the "UX OpwnGL Framework" project.
//	See copyright notice in "UX_GL_Framework.h".
//-------------------------------------------------------------------

#pragma once

#include "VoxelHelpers.h"

namespace Voxels
{

	//
	// Voxels Block
	//

	class VoxelsBlock : protected CReferencedObject
	{
	// types
	public:
		typedef uni_array< uint8 >			VoxelsUniData_t;
		typedef uni_array< const uint8 >	VoxelsUniConstData_t;

		struct BlockInfo
		{
			uvec3			blockSize;
			uvec2			tex2dSize;		// size of 2d texture wich contains 3d texture.
			uvec2			numLayers;		// number of z layers in X and Y axis.
			uint			xLayersPOT;		// power of 2 of X layers.
			uint			bytePerVoxel;	// if bpv > sizeof(RGBA32UI) then width of block will be scaled.
			uint			pixelPerVoxel;
			uint			border;
			cl_img_format	clFormat;
			///
			BlockInfo (uint border = 0) :
				bytePerVoxel(0), pixelPerVoxel(0), border(border), xLayersPOT(0)
			{}
		};


	// variables
	private:
		binary_array_t			_data;
		Ptr< const BlockInfo >	_info;


	// methods
	public:
		VoxelsBlock () : _info(null) {}

		explicit
		VoxelsBlock (const BlockInfo &info);
		
		bool Create ();
		bool Copy (const VoxelsBlock &other);

		bool IsValid () const;
		
		VoxelsUniData_t			Data ()							{ return _data; }
		VoxelsUniConstData_t	Data ()					const	{ return _data; }

		uvec3 const &			Dimension ()			const	{ return _info->blockSize; }
		uvec3					DimensionWithBorder ()	const	{ return Dimension() + Border() * 2; }
		uvec3					Border ()				const	{ return uvec3(_info->border); }
		uint					BytePerVoxel ()			const	{ return _info->bytePerVoxel; }
		uvec2 const &			TextureSize ()			const	{ return _info->tex2dSize; }
		uvec2 const &			NumZLayers ()			const	{ return _info->numLayers; }
		cl_img_format const&	CLFormat ()				const	{ return _info->clFormat; }
		uint					PixelPerVoxel ()		const	{ return _info->pixelPerVoxel; }

		bool SetDataRow (const ivec2 &startYZ, VoxelsUniConstData_t data);
		bool GetDataRow (const ivec2 &startYZ, OUT VoxelsUniData_t &data);
		bool GetDataRow (const ivec2 &startYZ, OUT VoxelsUniConstData_t &data) const;

		bool SetVoxel (const ivec3 &coord, VoxelsUniConstData_t data);

		template <typename TVoxel>  TVoxel &		GetVoxel (const ivec3 &coord);
		template <typename TVoxel>  TVoxel const &	GetVoxel (const ivec3 &coord) const;
		VoxelsUniConstData_t						GetVoxel (const ivec3 &coord) const;

		usize			GetRowPitch ()	const;
		static Bytes	GetRowAlign ()			{ return Bytes(4); }
		
	private:
		static usize	_GetImageSize (const uvec2 &tex2dSize, Bytes bpp);

		const void *	_GetVoxelPtr (const ivec3 &coord) const;

		uvec2			_Convert3DTo2D (const ivec3 &coord) const;

		Bytes			_TextureBytePerPixel ()	const	{ return Bytes( BytePerVoxel() / PixelPerVoxel() ); }
		
		void operator = (const VoxelsBlock &) {}
	};


	SHARED_POINTER( VoxelsBlock );
	
		
/*
=================================================
	constructor
=================================================
*/
	inline VoxelsBlock::VoxelsBlock (const BlockInfo &info) : _info(&info)
	{
	}

/*
=================================================
	Create
=================================================
*/
	inline bool VoxelsBlock::Create ()
	{
		const usize	data_size = _GetImageSize( TextureSize(), _TextureBytePerPixel() );

		if ( data_size != _data.Size() )
		{
			CHECK_ALLOC( _data.Resize( data_size ) );
		}
		return IsValid();
	}
	
/*
=================================================
	Copy
=================================================
*/
	inline bool VoxelsBlock::Copy (const VoxelsBlock &other)
	{
		this->_data.Copy( other._data );
		this->_info = other._info;
		return true;
	}

/*
=================================================
	IsValid
=================================================
*/
	inline bool VoxelsBlock::IsValid () const
	{
		const usize	data_size = _GetImageSize( TextureSize(), _TextureBytePerPixel() );

		return	All( IsPOT( Dimension() ) )		and
				All( Dimension() > uvec3(0) )	and
				BytePerVoxel() > 0				and
				data_size == _data.Size()		and
				Helpers::IsTex2DSizeValid( TextureSize(), NumZLayers(), DimensionWithBorder(), PixelPerVoxel() );
	}
	
/*
=================================================
	SetDataWithoutBorder
=================================================
*/
	inline bool VoxelsBlock::SetDataRow (const ivec2 &startYZ, VoxelsUniConstData_t data)
	{
		const ivec3	start	 = ivec3( 0, startYZ );
		const usize	row_size = AlignedImageRowSize( Dimension().x, Bytes(BytePerVoxel()).ToBits(), GetRowAlign() );

		CHECK_ARGUMENT( data.Size() == row_size );
		CHECK_ARGUMENT( All( start > ivec3(0) ) and All( start < Dimension().To<ivec3>() ) );

		void * row = (void *) _GetVoxelPtr( start );

		MemCopy( row, data.ptr(), data.Size() );
		return true;
	}
	
/*
=================================================
	SetDataWithoutBorder
=================================================
*/
	inline bool VoxelsBlock::SetVoxel (const ivec3 &coord, VoxelsUniConstData_t data)
	{
		CHECK_ARGUMENT( All( coord + Border().To<ivec3>() >= ivec3(0) ) );
		CHECK_ARGUMENT( All( coord + Border().To<ivec3>() < DimensionWithBorder().To<ivec3>() ) );
		
		void * row = (void *) _GetVoxelPtr( coord );

		MemCopy( row, data.ptr(), data.Size() );
		return true;
	}
	
/*
=================================================
	GetDataRow
=================================================
*/
	inline bool VoxelsBlock::GetDataRow (const ivec2 &startYZ, OUT VoxelsUniConstData_t &data) const
	{
		const ivec3	start = ivec3( 0, startYZ );

		CHECK_ARGUMENT( All( start >= ivec3(0) ) and All( start < Dimension().To<ivec3>() ) );

		const usize	row_size = AlignedImageRowSize( Dimension().x, Bytes(BytePerVoxel()).ToBits(), GetRowAlign() );
		
		data = VoxelsUniConstData_t( (const uint8 *)_GetVoxelPtr( start ), row_size );
		return true;
	}
	
/*
=================================================
	GetDataRow
=================================================
*/
	inline bool VoxelsBlock::GetDataRow (const ivec2 &startYZ, OUT VoxelsUniData_t &data)
	{
		const ivec3	start = ivec3( 0, startYZ );

		CHECK_ARGUMENT( All( start >= ivec3(0) ) and All( start < Dimension().To<ivec3>() ) );

		const usize	row_size = AlignedImageRowSize( Dimension().x, Bytes(BytePerVoxel()).ToBits(), GetRowAlign() );
		
		data = VoxelsUniData_t( (uint8 *)_GetVoxelPtr( start ), row_size );
		return true;
	}

/*
=================================================
	GetVoxel
=================================================
*/
	template <typename TVoxel>
	inline TVoxel &  VoxelsBlock::GetVoxel (const ivec3 &coord)
	{
		return * (TVoxel *) _GetVoxelPtr( coord );
	}
		
/*
=================================================
	GetVoxel
=================================================
*/
	template <typename TVoxel>
	inline TVoxel const &  VoxelsBlock::GetVoxel (const ivec3 &coord) const
	{
		return * (const TVoxel *) _GetVoxelPtr( coord );
	}

/*
=================================================
	GetVoxel
=================================================
*/
	inline VoxelsBlock::VoxelsUniConstData_t  VoxelsBlock::GetVoxel (const ivec3 &coord) const
	{
		return VoxelsUniConstData_t( (const uint8 *) _GetVoxelPtr( coord ), BytePerVoxel() );
	}

/*
=================================================
	_GetImageSize
=================================================
*/
	inline usize  VoxelsBlock::_GetImageSize (const uvec2 &tex2dSize, Bytes bpp)
	{
		return AlignedImageDataSize( uvec3( tex2dSize, 1 ), bpp.ToBits(), GetRowAlign() );
	}
	
/*
=================================================
	_GetVoxelPtr
=================================================
*/
	inline const void *  VoxelsBlock::_GetVoxelPtr (const ivec3 &coord) const
	{
		const usize	offset	= GetPixelOffset( uvec3(_Convert3DTo2D( coord ), 0), DimensionWithBorder(),
											  Bytes(BytePerVoxel()), GetRowAlign() );
		ASSERT( offset < _data.Size() );
		return PointerCast< const void *>( MovePointer( _data.ptr(), offset ) );
	}
	
/*
=================================================
	_Convert3DTo2D
=================================================
*/
	inline uvec2 VoxelsBlock::_Convert3DTo2D (const ivec3 &coord) const
	{
		return Helpers::Convert3DTo2D( coord, NumZLayers(), Dimension(), Border(), PixelPerVoxel() );
	}

/*
=================================================
	GetRowPitch
=================================================
*/
	inline usize  VoxelsBlock::GetRowPitch () const
	{
		return AlignedImageRowSize( TextureSize().x, _TextureBytePerPixel().ToBits(), GetRowAlign() );
	}


}	// Voxels